Appearance
[中文版本](/posts/c教程/zh-CN/07-贴近系统/065-Signals and Signal Handlers)
When you press Ctrl+C and your program stops, that’s not magic, it’s a signal. Signals are how the operating system tells your process that something important has happened.
They’re asynchronous, lightweight messages from the kernel or other processes. Your C program can catch, ignore, or handle them, giving you full control over shutdowns, interrupts, and errors.
Step 1. What Is a Signal?
A signal is an integer code sent to a process to notify it of an event.
| Signal | Meaning | Default Action |
|---|---|---|
SIGINT | Interrupt (Ctrl+C) | Terminate |
SIGTERM | Termination request | Terminate |
SIGKILL | Forced kill (cannot catch) | Terminate immediately |
SIGSEGV | Invalid memory access | Core dump |
SIGCHLD | Child process exited | Ignore or handle |
SIGALRM | Timer expired | Terminate |
SIGUSR1,SIGUSR2 | User-defined | Terminate (unless handled) |
You can list all signals:
kill -lOutput example:
1) SIGHUP 2) SIGINT 3) SIGQUIT 9) SIGKILL 15) SIGTERM ...Step 2. Sending Signals
Any process can send a signal to another using thekill() system call.
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
pid_t pid = getpid();
printf("My PID: %d\n", pid);
pause(); // wait for signal
}Run this in one terminal:
./signal_waitThen in another:
kill -SIGUSR1 <pid>The program will wake up frompause() and terminate (default behavior for SIGUSR1).
Step 3. Installing a Signal Handler
You can override the default action by installing a handler function.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handle_sigint(int sig) {
printf("\nCaught signal %d (SIGINT). Exiting gracefully.\n", sig);
_exit(0);
}
int main(void) {
signal(SIGINT, handle_sigint);
while (1) {
printf("Running... Press Ctrl+C to stop.\n");
sleep(1);
}
}Compile and run:
gcc sigint_demo.c -o sigint_demo
./sigint_demoOutput:
Running... Press Ctrl+C to stop.
Running... Press Ctrl+C to stop.
^C
Caught signal 2 (SIGINT). Exiting gracefully.Step 4. Using sigaction() (Modern API)
signal() is simple but inconsistent across systems. The recommended modern interface issigaction().
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int sig) {
write(STDOUT_FILENO, "Caught signal\n", 14);
}
int main(void) {
struct sigaction sa = {0};
sa.sa_handler = handler;
sigaction(SIGUSR1, &sa, NULL);
printf("PID: %d\n", getpid());
while (1) pause();
}Send a signal:
kill -SIGUSR1 <pid>Output:
Caught signalUnlikesignal(), this version is reliable and reentrant-safe (you can call only async-safe functions likewrite() inside handlers).
Step 5. Ignoring and Resetting Signals
You can ignore a signal:
signal(SIGINT, SIG_IGN);Or reset to default behavior:
signal(SIGINT, SIG_DFL);This can be useful if you don’t want Ctrl+C to interrupt certain sections of code.
Step 6. Sending Signals to Other Processes
Example: parent signaling its child.
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void child_handler(int sig) {
printf("Child got signal %d\n", sig);
}
int main(void) {
pid_t pid = fork();
if (pid == 0) {
signal(SIGUSR1, child_handler);
while (1) pause();
} else {
sleep(1);
printf("Parent sending SIGUSR1\n");
kill(pid, SIGUSR1);
sleep(1);
kill(pid, SIGTERM);
}
}Output:
Parent sending SIGUSR1
Child got signal 10Step 7. Blocking and Unblocking Signals
Sometimes you want to delay signal handling. You can usesigprocmask() to block signals temporarily.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigprocmask(SIG_BLOCK, &set, NULL);
printf("SIGINT blocked for 5 seconds...\n");
sleep(5);
printf("Unblocking now.\n");
sigprocmask(SIG_UNBLOCK, &set, NULL);
while (1) pause();
}Press Ctrl+C during the block, nothing happens. Once unblocked, it terminates normally.
Step 8. Timers with alarm() and SIGALRM
You can set a timer that sends a signal after a delay.
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig) {
printf("Timer expired!\n");
}
int main(void) {
signal(SIGALRM, handler);
alarm(3); // after 3 seconds, send SIGALRM
printf("Waiting...\n");
pause();
}Output:
Waiting...
Timer expired!Step 9. Cleaning Up on Exit
Signals let you implement graceful cleanup (e.g., close files, delete temp files).
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void cleanup(int sig) {
printf("\nCleaning up before exit...\n");
unlink("tempfile.tmp");
_exit(0);
}
int main(void) {
signal(SIGINT, cleanup);
open("tempfile.tmp", O_CREAT | O_WRONLY, 0644);
while (1) {
printf("Running... (Ctrl+C to exit)\n");
sleep(1);
}
}Now pressing Ctrl+C removes the temporary file safely.
Tiny Code: Graceful Shutdown Server
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
volatile sig_atomic_t running = 1;
void stop(int sig) {
running = 0;
}
int main(void) {
signal(SIGINT, stop);
printf("Server running. Press Ctrl+C to stop.\n");
while (running) {
printf("Handling request...\n");
sleep(1);
}
printf("Server shutting down cleanly.\n");
}Step 10. Why It Matters
Signals are essential for:
- Graceful termination (Ctrl+C)
- Timeouts and alarms
- Child process monitoring (
SIGCHLD) - Error handling (segmentation faults)
- Daemon and server control
Every real-world Unix program, from editors to web servers, depends on correct signal handling for stability.
Try It Yourself
- Write a program that ignores SIGINT for 5 seconds, then restores default behavior.
- Catch SIGTERM and print “Termination requested”.
- Make a parent send SIGUSR1 to its child every second.
- Use
alarm()to implement a timeout for user input. - Add a signal handler to your shell that cleans up child processes before exit.
Next, you’ll learn how programs share and map memory directly, usingmmap(), a system call that powers databases, shared memory, and file-backed data structures.
